#pragma once
#include "Socket.hpp"
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <algorithm>
#include "log.hpp"
using namespace LogModule;
using namespace SocketModule;
class SelectServe
{
    const static int size = sizeof(fd_set) * 8;

public:
    SelectServe(int port)
        : _isruning(false),
          _listensock(std::make_unique<TcpSocket>(port)),
          _port(port),
          _fdarrays(size, -1)
    {
        _listensock->BuildTcpSocketMethod(port);
        int i = 0;
        // 把我们的监听套接字加入我们的辅助数组
        for (i = 0; i < size; i++)
        {
            if (_fdarrays[i] == -1)
            {
                break;
            }
        }
        _fdarrays[i] = _listensock->getfd();
    }
    ~SelectServe()
    {
    }
    void Start()
    {
        _isruning = true;
        while (_isruning)
        {
            // 每次我们情况我们清楚我们关心事件集合
            fd_set readset; // 关心的事件集合
            FD_ZERO(&readset);
            int maxfd = -1;
            for (int i = 0; i < size; i++)
            {
                if (_fdarrays[i] != -1)
                {
                    if (_fdarrays[i] > maxfd)
                    {
                        maxfd = _fdarrays[i];
                    }
                    // 把辅助数组里面我们关心的文件描述符设置到我们的集合中
                    FD_SET(_fdarrays[i], &readset);
                }
            }
            ShowFd();
            LOG(LogLevel::DEBUG) << "MAX_FD:" << maxfd;
            struct timeval timeout;
            timeout.tv_sec = 1;
            timeout.tv_usec = 0;
            // 就绪返回不代表就是对的 错误也会立马返回我们要对结果进行判断
            //int n = select(maxfd + 1, &readset, nullptr, nullptr, &timeout);
            int n = select(maxfd + 1, &readset, nullptr, nullptr, nullptr);
            switch (n)
            {
            case 0:
                // 此时超时返回
                LOG(LogLevel::DEBUG) << "timeout...";
                break;

            case -1:
                // 出现错误
                LOG(LogLevel::DEBUG) << "select erroe...";
                break;

            default:
                LOG(LogLevel::DEBUG) << "有事件就绪了...";
                // 此时判断那个文件描述符就位了
                Dispatcher(readset); // 处理就绪事件
                break;
            }
        }
    }
    void Dispatcher(fd_set rdset)
    {
        // 下面遍历我们的复制数组看一下那个文件描述符就位
        for (int i = 0; i < size; i++)
        {
            if (_fdarrays[i] != -1)
            {
                //如果该文件描述符被设置了就说明就绪了
                if (FD_ISSET(_fdarrays[i], &rdset))
                {
                    // 此时判断是监听套接字还是普通套接字
                    if (_fdarrays[i] == _listensock->getfd())
                    {
                        // 此时套接字
                        Accept();
                    }
                    else
                    {
                        Rece(i);
                    }
                }
            }
        }
    }
    void Accept()
    {
        InetAddr client;
        int sockfd = _listensock->acceptordie(&client);
        if (sockfd > 0)
        {
            LOG(LogLevel::INFO) << "get a new link, sockfd: "
                                << sockfd << ", client is: " << client.StringAddr();
            int pos = 0;
            for (; pos < size; pos++)
            {
                if (_fdarrays[pos] == -1)
                    break;
            }
            if (pos == size)
            {
                LOG(LogLevel::WARNING) << "select server full"; // 此时可以进行我们的扩容
                // 关闭我们的文件描述符
                close(sockfd);
            }
            else
            {
                // 设置我们普通文件描述符进入辅助数组
                _fdarrays[pos] = sockfd;
            }
        }
    }
    void Rece(int pos)
    {
        int fd = _fdarrays[pos];
        char buffer[1024];
        int n = read(fd, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client say@ " << buffer << std::endl;
        }
        else if (n == 0)
        {
            LOG(LogLevel::INFO) << "clien quit...";
            // 1. 不要让select在关系这个fd了
            _fdarrays[pos] = -1;

            // 2. 关闭fd
            close(fd);
        }
        else
        {
            LOG(LogLevel::ERROR) << "recv error";
            // 1. 不要让select在关系这个fd了
            _fdarrays[pos] = -1;
            // 2. 关闭fd
            close(fd);
        }
    }
    void ShowFd()
    {

        for (int i = 0; i < size; i++)
        {
            if (_fdarrays[i] == -1)
                continue;
            std::cout << _fdarrays[i] << " ";
        }
        std::cout << std::endl;
    }

private:
    bool _isruning;
    std::unique_ptr<Socket> _listensock;
    int _port;
    std::vector<int> _fdarrays;
};